// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: jingyuan

#pragma once

#include <fstream>
#include <iostream>
#include <optional>
#include <string>

#include "coin2/base/math/time_moving_window.h"
#include "coin2/base/time.h"

namespace coin2::exchange::sim::order_v1 {

using coin2::base::math::TimeMovingWindow;

struct SimpleMean {
  void OnPopped(int64_t /*time*/, int64_t latency) {
    counter_ -= 1;
    if (counter_ == 0) {
      last_mean_ = latency;
    }
    latency_sum_ -= latency;
  }
  void OnPushed(int64_t /*time*/, int64_t latency) {
    counter_ += 1;
    latency_sum_ += latency;
  }

  std::optional<int64_t> mean() {
    if (counter_ == 0 || latency_sum_ == 0) {
      return last_mean_;
    }
    last_mean_ = static_cast<double>(latency_sum_) / counter_;
    return last_mean_;
  }

  int64_t latency_sum_{0};
  int counter_{0};
  std::optional<int64_t> last_mean_{std::nullopt};
};

class BacktestingLatency {
 public:
  BacktestingLatency(const std::string& latency_csv_path, int64_t window_size_ns)
      : latency_csv_path_(latency_csv_path), half_window_size_ns_{window_size_ns / 2} {
    ma_.ResizeWindow(window_size_ns);
  }

  std::optional<int64_t> GetLatency(int64_t timestamp) {
    // CHECK_GT(timestamp, last_ts) << "timestamp must be increasing";
    last_ts = timestamp;
    if (current_date_.is_not_a_date_time() ||
        current_date_.date() != PtimeFromTimestamp(timestamp + half_window_size_ns_).date()) {
      current_date_ = PtimeFromTimestamp(timestamp + half_window_size_ns_);
      InitCsvForDate(timestamp + half_window_size_ns_);
    }
    char ch;
    while (latency_file_.peek() != EOF && timestamp + half_window_size_ns_ > next_timestamp) {
      ma_.Push(next_timestamp, next_latency);
      latency_file_ >> next_timestamp >> ch >> next_latency;
      LOG(INFO) << next_timestamp << " " << next_latency;
      LOG(INFO) << ma_.handler().mean();
    }
    ma_.PushTime(timestamp + half_window_size_ns_);
    return ma_.handler().mean();
  }

 private:
  void InitCsvForDate(int64_t timestamp) {
    if (latency_file_.is_open()) {
      latency_file_.close();
    }
    std::string new_csv_file = std::string(latency_csv_path_);
    new_csv_file.replace(new_csv_file.find("{date}"), 6, ::impl::YmdFromTimestamp(timestamp));
    if (std::experimental::filesystem::exists(new_csv_file)) {
      latency_file_.open(new_csv_file);
      std::string s;
      latency_file_ >> s;
      char ch;
      latency_file_ >> next_timestamp >> ch >> next_latency;
    } else {
      LOG(ERROR) << fmt::format(
          "Failed to open latency file {}, default latency will be used",
          new_csv_file);
    }
  }
  std::string latency_csv_path_;
  std::ifstream latency_file_;
  boost::posix_time::ptime current_date_;
  int64_t last_ts{0};
  int64_t next_timestamp{0};
  int64_t next_latency{0};
  int64_t half_window_size_ns_;

  TimeMovingWindow<int64_t, SimpleMean> ma_;
};

}  // namespace coin2::exchange::sim::order_v1
